package baseFx.database.mybatis;

import baseFx.common.AutoCloseable;
import baseFx.common.utils.ClassUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class UseCursorInterceptor implements Interceptor {
    private static ThreadLocal<Integer> tFetchSize = new ThreadLocal<>();
    private static ThreadLocal<Connection> tConnection = new ThreadLocal<>();
    private Field cursorConnField = null;
    private boolean skipCursor = false;
    private String defUseCursor;
    private String defUseSrvStmt;
    private Class<?> mysqlClass;

    public boolean fixMysqlCursorProp;
    public boolean closeFixMysqlCursorPropTip;

    public UseCursorInterceptor() {
        mysqlClass = ClassUtils.getClassForName("com.mysql.cj.jdbc.ConnectionImpl");
    }

    public AutoCloseable set(Connection connection, int fetchSize) {
        tFetchSize.set(fetchSize);
        tConnection.set(connection);
        return () -> {
            if (skipCursor == false) {
                if (mysqlClass != null && fixMysqlCursorProp) {
                    mysqlSetProp(tConnection.get(), false, false);
                }
            }
            tConnection.remove();
            tFetchSize.remove();
        };
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Connection connection = tConnection.get();
        Integer size = tFetchSize.get();
        if (connection != null && size != null) {
            if (mysqlClass != null && skipCursor == false) {
                if (fixMysqlCursorProp) {
                    mysqlSetProp(connection, true, true);
                } else if (closeFixMysqlCursorPropTip == false) {
                    System.out.println("Mysql游标没有开启fixMysqlCursorProp，建议打开，设置closeFixMysqlCursorPropTip可关闭此提示。");
                }
            }
            Statement ps = (Statement) invocation.proceed();
            ps.setFetchSize(size);
            return ps;
        }
        return invocation.proceed();
    }

    private void mysqlSetProp(Connection connection, Boolean useCursor, Boolean useSrvStmt) {
        if (cursorConnField == null && mysqlClass.isAssignableFrom(connection.getClass()) == false) {
            List<Field> fs = ClassUtils.fields(connection.getClass());
            for (Field f : fs) {
                if (Connection.class.isAssignableFrom(f.getType())) {
                    cursorConnField = f;
                    break;
                }
            }
            if (cursorConnField == null) {
                skipCursor = true;
                return;
            }
        }
        Object conn = cursorConnField != null ? ClassUtils.getValue(cursorConnField, connection) : connection;
        if (mysqlClass.isInstance(conn)) {
            Object ps = ClassUtils.invoke(conn, "getPropertySet");
            Properties properties = (Properties) ClassUtils.invoke(ps, "exposeAsProperties");
            boolean set = false;
            if (defUseSrvStmt == null) {
                defUseSrvStmt = properties.getProperty("useServerPrepStmts", Boolean.FALSE.toString());
            }
            if (defUseSrvStmt.equalsIgnoreCase(Boolean.FALSE.toString())) {
                properties.setProperty("useServerPrepStmts", useSrvStmt.toString());
                set = true;
            }
            if (defUseCursor == null) {
                defUseCursor = properties.getProperty("useCursorFetch", Boolean.FALSE.toString());
            }
            if (defUseCursor.equalsIgnoreCase(Boolean.FALSE.toString())) {
                properties.setProperty("useCursorFetch", useCursor.toString());
                set = true;
            }
            if (set) {
                ClassUtils.invoke(ps, "initializeProperties", properties);
            } else {
                skipCursor = true;
            }
        }
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
